from tkinter import *
import os
from sklearn.neighbors import KNeighborsClassifier as kNN

# 用于暂存鼠标位置
pixel_x = 0
pixel_y = 0
# 绘图框的起始位置和比例
start_x = 40; start_y = 40; rate = 4
# 用于显示数字
num_display = 0
# 用于存储手写数字的点的坐标
pixel_locat = []
# 用于标记本次数据是否保存
save_flag = 0

top = Tk(className="手写输入")
photo = PhotoImage()
image = Canvas(top, bg="white", height=400, width=350)
image.create_image(400, 350, image=photo)
lable = Label(text=num_display, background="white", font=20)

# 单选框
radio_x = 0; radio_y = 0
radio_group = StringVar()
def Radiochecked():
    global button_clear
    if radio_group.get() == "classification":
        button_clear["text"] = "识别"
    else:
        button_clear["text"] = "测试"
    lable["text"] = num_display


radio1 = Radiobutton(top, text="训练模式", value="train", variable=radio_group, bg="white", command=Radiochecked)
radio2 = Radiobutton(top, text="识别模式", value="classification", variable=radio_group, bg="white", command=Radiochecked)
# 将绘图板的内容保存为Txt
def SaveToTxt(pixel_locat):
    Thickening_list = []      # 加粗的数组
    # 加粗某一个像素
    def creatPath(path): # 按照已有文件生成新文件路径
        list_file = os.listdir(path)
        new_path = "000.txt"
        max_fileName = 0
        for file in list_file:
            if file[1] != "_" and int(file[:3]) >= max_fileName:       # 本地数据集命名格式为0_1(0号第一个)
                new_path = "%03d" % (int(file[:3])+1)+".txt"
        return path+"/"+new_path
    # 加粗某一个像素点
    def Thichening(pixel, width=1):
        for y in range(pixel[1]-width, pixel[1]+width+1):
            for x in range(pixel[0]-width, pixel[0]+width+1):
                if [x, y] not in Thickening_list:
                    Thickening_list.append([x, y])
    # 判断像素区域有无像素点
    def judge(x, y):
        for x_t in range(x, x+rate*2+1):
            for y_t in range(y, y+rate*2+1):
                if [x_t, y_t] in pixel_locat:
                    return 1
        return 0
    # 压缩图像并保存
    fr = open("temp.txt", mode="w")
    if radio_group.get() == "train":
        fr = open(creatPath(str(num_display)), mode="w")
    # 先进行压缩的同时进行加粗
    temp_image = []
    for y in range(32):
        save_temp = []
        for x in range(32):
            pixel_t = judge(x*rate*2, y*rate*2)
            if pixel_t == 1:
                Thichening([x, y], 1)
            save_temp.append(pixel_t)
        temp_image.append(save_temp)
    # 保存为文本
    for y in range(32):
        save_temp = ""
        for x in range(32):
            if [x, y] in Thickening_list or [x, y] in temp_image:
                save_temp += "1"
            else:
                save_temp += "0"
        fr.write(save_temp+"\n")
    fr.close()

def NextClick():
    global num_display, pixel_locat
    # SaveWindow()     # 调用保存窗口，改变save_flag的值
    for item in image.find_all()[2:]:
        image.delete(item)
    SaveToTxt(pixel_locat)
    num_display += 1
    if num_display == 10:
        num_display = 0
    lable["text"] = num_display
    pixel_locat = []
button_next = Button(text="下一个", height=2, width=8, command=NextClick)

# 清空按钮
def ClearClick():
    global pixel_locat
    global TrainLable
    for item in image.find_all()[2:]:
        image.delete(item)
    if radio_group.get() == "classification":
        SaveToTxt(pixel_locat)
        for i in neigh.kneighbors([GetVector("temp.txt")])[1][0]:
            print(TrainLable[i])
        lable["text"] = neigh.predict([GetVector("temp.txt")])
    pixel_locat = []


button_clear = Button(text="测试", height=2, width=8, command=ClearClick)

# 填充两个像素点
def FillBlank(x0, y0, x1, y1):
    global pixel_locat
    fill_list = []
    fill_list.append([x0, y0])
    if x0 > x1:
        temp = x1; x1 = x0; x0 = temp
        temp = y1; y1 = y0; y0 = temp
    for x in range(x0, x1):
        y = round((y1-y0)/(x1-x0)*(x-x0) + y0)
        fill_list.append([x, y])
    if y0 > y1:
        temp = x1; x1 = x0; x0 = temp
        temp = y1; y1 = y0; y0 = temp
    for y in range(y0, y1):
        x = round(((x1-x0)/(y1-y0))*(y-y0) + x0)
        fill_list.append([x, y])
    # fill_list.append([x1, y1])   不能加会重复
    return fill_list


def onLeftButtonMove(event):
    global pixel_x, pixel_y
    image.create_line(pixel_x, pixel_y, event.x, event.y, fill="black", width=2)
    temp_pixel = FillBlank(pixel_x-start_x, pixel_y-start_y, event.x-start_x, event.y-start_y)
    for pixel in temp_pixel:
        if pixel not in pixel_locat:
            pixel_locat.append(pixel)
    pixel_x = event.x
    pixel_y = event.y
image.bind('<B1-Motion>', onLeftButtonMove)


def onLeftButtonDown(event):
    global pixel_x, pixel_y
    pixel_x = event.x
    pixel_y = event.y
image.bind('<Button-1>', onLeftButtonDown)

def SaveWindow():
    def SaveClick():
        global save_flag
        save_flag = 1
        savefamr.quit()
        savefamr.destroy()
    def CancelClick():
        global save_flag
        save_flag = 0
        savefamr.quit()
        savefamr.destroy()
    savefamr = Tk(className=" ")
    savefamr.geometry("380x200+40+40")
    liable1 = Label(savefamr, text="是否保存此次手写数据至训练集?", font=20)
    liable1.place(x=0, y=20)
    button1 = Button(savefamr, text="保存", height=2, width=9, command=SaveClick)
    button2 = Button(savefamr, text="取消", height=2, width=9, command=CancelClick)
    button1.place(x=20, y=70)
    button2.place(x=200, y=70)
    savefamr.mainloop()


# 对手写数字进行分类
neigh = kNN(n_neighbors=5, algorithm='auto')    # KNN分类器
TrainMat = []  # 训练集矩阵
TrainLable = []  # 训练集标签
def GetVector(path):
    vector = []
    fr = open(path, mode="r")
    for line in fr.readlines():
        for i in range(32):
            vector.append(int(line[i]))
    return vector
def classifNum():
    global neigh
    # 将某一个文件的转化为向量形式
    # 获得训练集
    for num in range(10):           #所有的数字文件夹
        for file in os.listdir(str(num)):
            TrainLable.append(num)          # 标记标签
            TrainMat.append(GetVector(str(num)+"/"+file))
    neigh.fit(TrainMat, TrainLable)

if __name__ == "__main__":
    # 放置各种控件
    image.pack()
    button_next.place(x=start_x, y=start_y+64*rate+20)
    button_clear.place(x=start_x+64*rate-65, y=start_y+64*rate+20)
    lable.place(x=int((start_x+start_x+64*rate-65)/2), y=start_y+64*rate+30)
    image.create_rectangle(start_x, start_y, start_x+64*rate, start_y+64*rate, width=3,outline="red")
    radio1.place(x=radio_x, y=radio_y)
    radio2.place(x=radio_x+70, y=radio_y)
    radio_group.set("train")            # 默认为train模式
    top.iconbitmap("myicon.ico")        # 图标
    top.resizable(False, False)         # 不可更改大小
    classifNum()                        # 对分类器进行初始化
    top.mainloop()


